<template>
    <div id="global-uploader">
        <!-- 上传 -->
        <uploader
                ref="uploader"
                :options="options"
                :autoStart="false"
                @file-added="onFileAdded"
                @file-success="onFileSuccess"
                @file-progress="onFileProgress"
                @file-error="onFileError"
                class="uploader-app">
            <uploader-unsupport></uploader-unsupport>

            <uploader-btn id="global-uploader-btn" :attrs="attrs" ref="uploadBtn">选择文件</uploader-btn>

            <uploader-list v-show="panelShow">
                <div class="file-panel" slot-scope="props" :class="{'collapse': collapse}">
                    <div class="file-title">
                        <h2>文件列表</h2>
                        <div class="operate">
                            <el-button @click="fileListShow" :title="collapse ? '展开':'折叠' ">
                                折叠/展开
                            </el-button>
                            <el-button @click="close" title="关闭">
                                关闭
                            </el-button>
                        </div>
                    </div>

                    <ul class="file-list">
                        <li v-for="file in props.fileList" :key="file.id">
                            <uploader-file :class="'file_' + file.id" ref="files" :file="file" :list="true"></uploader-file>
                        </li>
                        <div class="no-file" v-if="!props.fileList.length"><i class="iconfont icon-empty-file"></i> 暂无待上传文件</div>
                    </ul>
                </div>
            </uploader-list>

        </uploader>

    </div>
</template>

<script>

    /**
     *   全局上传插件
     *   调用方法：eventBus.$emit('openUploader', {}) 打开文件选择框，参数为需要传递的额外参数
     *   监听函数：eventBus.$on('fileAdded', fn); 文件选择后的回调
     *            eventBus.$on('fileSuccess', fn); 文件上传成功的回调
     */
    import {ACCEPT_CONFIG} from '../fileUpload/config';
    import eventBus from '@/eventBus.js'
    import SparkMD5 from 'spark-md5';
    export default {
        data() {
            return {
                options: {
                    // 额外的一些请求头，如果是函数的话，则会传入 Uploader.File 实例、当前块 Uploader.Chunk 以及是否是测试模式
                    headers: {
                        Authorization: ''
                    },
                    target: "/resources/uploader/chunk",  // 接口地址
                    chunkSize: '2048000',  // 分块时按照该值来分，最后一个上传块的大小是可能是大于等于1倍的这个值但是小于两倍的这个值大小
                    forceChunkSize: false,  // 是否强制所有的块都是小于等于 chunkSize 的值
                    fileParameterName: 'file', // 文件参数名称
                    maxChunkRetries: 3, // 最大自动失败重试上传次数，值可以是任意正整数，如果是 undefined 则代表无限次
                    simultaneousUploads: 3,  // 并发上传数
                    singleFile: false,  // 单文件上传，覆盖式，如果选择了多个会把之前的取消掉
                    testChunks: false,   //是否开启服务器分片校验
                    withCredentials: false,  // 标准的 CORS 请求是不会带上 cookie 的，如果想要带的话需要设置 withCredentials 为 true
                    method: 'multipart/form-data',  // 当上传的时候所使用的是方式，可选 multipart、octet
                    // testMethod: 'GET',
                    // uploadMethod: 'POST',
                    allowDuplicateUploads: false,  // 如果说一个文件已经上传过了是否还允许再次上传。默认的话如果已经上传了，除非你移除了否则是不会再次重新上传的
                    prioritizeFirstAndLastChunk: false, // 对于文件而言是否高优先级发送第一个和最后一个块。一般用来发送到服务端，然后判断是否是合法文件；例如图片或者视频的 meta 数据一般放在文件第一部分，这样可以根据第一个块就能知道是否支持

                    // 重试间隔，值可以是任意正整数，如果是 null 则代表立即重试
                    chunkRetryInterval: null,
                    // 进度回调间隔
                    progressCallbacksInterval: 500,
                    // 主要用于计算平均速度，值就是从 0 到 1，如果是 1 那么上传的平均速度就等于当前上传速度，如果说长时间上传的话，建议设置为 0.02，这样剩余时间预估会更精确，这个参数是需要和 progressCallbacksInterval 一起调整的
                    speedSmoothingFactor: 0.1,
                    // 认为响应式成功的响应码
                    successStatuses: [200, 201, 202],
                    // 认为是出错的响应码
                    permanentErrors: [404, 415, 500, 501],
                    // 初始文件 paused 状态
                    initialPaused: false,

                    // 服务器分片校验函数，秒传及断点续传基础
                    checkChunkUploadedByResponse: function (chunk, message) {
                        let objMessage = JSON.parse(message);
                        if (objMessage.skipUpload) {
                            return true;
                        }
                        return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
                    },

                    // 其他额外的参数，这个可以是一个对象或者是一个函数，如果是函数的话，则会传入 Uploader.File 实例、当前块 Uploader.Chunk 以及是否是测试模式
                    query() {},

                    // 用于格式化你想要剩余时间，一般可以用来做多语言
                    parseTimeRemaining: function (timeRemaining, parsedTimeRemaining) {
                        // timeRemaining{Number}, 剩余时间，秒为单位
                        // parsedTimeRemaining{String}, 默认展示的剩余时间内容
                        return parsedTimeRemaining
                        .replace(/\syears?/, '年')
                        .replace(/\days?/, '天')
                        .replace(/\shours?/, '小时')
                        .replace(/\sminutes?/, '分钟')
                        .replace(/\sseconds?/, '秒')
                    }
                },
                attrs: {
                    accept: ACCEPT_CONFIG.getAll()
                },
                panelShow: false,   //选择文件后，展示上传panel
                collapse: true
            }
        },
        mounted() {
            eventBus.$on('openUploader', query => {
                this.options.headers.Authorization = 'Bearer '+ window.sessionStorage.getItem('access_token');
                this.params = query || {};
                if (this.$refs.uploadBtn) {
                    $('#global-uploader-btn').click();
                }
            });
        },
        computed: {
            //Uploader实例
            uploader() {
                return this.$refs.uploader.uploader;
            }
        },
        methods: {
            onFileAdded(file) {
                this.panelShow = true;
                this.computeMD5(file);
                eventBus.$emit('fileAdded');
            },
            onFileProgress(rootFile, file, chunk) {
                console.log(`上传中 ${file.name}，chunk：${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
            },
            onFileSuccess(rootFile, file, response, chunk) {
                let res = JSON.parse(response);
                if (res.status=='500') {
                    this.$message({ message: res.msg, type: 'error' });
                    this.statusSet(file.id, 'failed');
                    return;
                }
                if (res.code=='401') {
                    this.$message({ message: '没有权限访问', type: 'error' });
                    this.statusSet(file.id, 'failed');
                    return;
                }
                // 服务器自定义的错误（即虽返回200，但是是错误的情况），这种错误是Uploader无法拦截的
                if (!res.status) {
                    this.$message({ message: res.message, type: 'error' });
                    // 文件状态设为“失败”
                    this.statusSet(file.id, 'failed');
                    return
                }
                console.log(res);
                // 如果服务端返回需要合并  needMerge 是否需要合并
                if (res.data.needMerge) {
                    // 文件状态设为“合并中”
                    this.statusSet(file.id, 'merging');
                    let fileInfo = {
                      filename:res.data.filename,
                      identifier:res.data.identifier,
                      totalSize:res.data.totalSize,
                      type:res.data.type,
                      instanceId:res.data.instanceId,
                      modelChunk:res.data.modelChunk
                    };
                    this.postRequest("/resources/uploader/mergeFile",fileInfo).then(resp => {
                        if (resp) {
                            eventBus.$emit('fileSuccess');
                            this.statusRemove(file.id);
                            console.log("文件合并成功");
                        }
                    });
                // 不需要合并
                } else {
                    eventBus.$emit('fileSuccess');
                    this.statusSet(file.id, 'success');
                }
            },
            onFileError(rootFile, file, response, chunk) {
                this.$message({
                    message: response,
                    type: 'error'
                })
            },
            /**
             * 计算md5，实现断点续传及秒传
             * @param file
             */
            computeMD5(file) {
                let fileReader = new FileReader();
                let time = new Date().getTime();
                let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
                let currentChunk = 0;
                const chunkSize = 10 * 1024 * 1000;
                let chunks = Math.ceil(file.size / chunkSize);
                let spark = new SparkMD5.ArrayBuffer();
                // 文件状态设为"计算MD5"
                this.statusSet(file.id, 'md5');
                file.pause();
                loadNext();
                fileReader.onload = (e => {
                    spark.append(e.target.result);
                    if (currentChunk < chunks) {
                        currentChunk++;
                        loadNext();
                        // 实时展示MD5的计算进度
                        this.$nextTick(() => {
                            $(`.myStatus_${file.id}`).text('校验MD5 '+ ((currentChunk/chunks)*100).toFixed(0)+'%')
                        })
                    } else {
                        let md5 = spark.end();
                        this.computeMD5Success(md5, file);
                        console.log(`MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${new Date().getTime() - time} ms`);
                    }
                });
                fileReader.onerror = function () {
                    this.error(`文件${file.name}读取出错，请检查该文件`)
                    file.cancel();
                };
                function loadNext() {
                    let start = currentChunk * chunkSize;
                    let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
                    fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
                }
            },
            computeMD5Success(md5, file) {
                // 将自定义参数直接加载uploader实例的opts上
                Object.assign(this.uploader.opts, {
                    query: {
                        ...this.params,
                    }
                })
                file.uniqueIdentifier = md5;
                file.resume();
                this.statusRemove(file.id);
            },
            fileListShow() {
                let $list = $('#global-uploader .file-list');
                if ($list.is(':visible')) {
                    $list.slideUp();
                    this.collapse = true;
                } else {
                    $list.slideDown();
                    this.collapse = false;
                }
            },
            close() {
                this.uploader.cancel();
                this.panelShow = false;
            },
            /**
             * 新增的自定义的状态: 'md5'、'transcoding'、'failed'
             * @param id
             * @param status
             */
            statusSet(id, status) {
                let statusMap = {
                    md5: {
                        text: '校验MD5',
                        bgc: '#fff'
                    },
                    merging: {
                        text: '合并中',
                        bgc: '#e2eeff'
                    },
                    transcoding: {
                        text: '转码中',
                        bgc: '#e2eeff'
                    },
                    failed: {
                        text: '上传失败',
                        bgc: '#e2eeff'
                    },
                    success: {
                        text: '上传成功',
                        bgc: '#e2eeff'
                    }
                }
                this.$nextTick(() => {
                    $(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({
                        'position': 'absolute',
                        'top': '0',
                        'left': '0',
                        'right': '0',
                        'bottom': '0',
                        'zIndex': '1',
                        'backgroundColor': statusMap[status].bgc
                    }).text(statusMap[status].text);
                })
            },
            statusRemove(id) {
                this.$nextTick(() => {
                    $(`.myStatus_${id}`).remove();
                })
            },
            error(msg) {
                this.$notify({
                    title: '错误',
                    message: msg,
                    type: 'error',
                    duration: 2000
                })
            }
        },
        watch: {},
        destroyed() {
            eventBus.$off('openUploader');
        },
        components: {}
    }
</script>

<style scoped lang="scss">
    #global-uploader {
        position: fixed;
        z-index: 20;
        right: 15px;
        bottom: 15px;
        .uploader-app {
            width: 520px;
        }
        .file-panel {
            background-color: #fff;
            border: 1px solid #e2e2e2;
            border-radius: 7px 7px 0 0;
            box-shadow: 0 0 10px rgba(0, 0, 0, .2);
            .file-title {
                display: flex;
                height: 40px;
                line-height: 40px;
                padding: 0 15px;
                border-bottom: 1px solid #ddd;
                .operate {
                    flex: 1;
                    text-align: right;
                }
            }
            .file-list {
                position: relative;
                height: 240px;
                overflow-x: hidden;
                overflow-y: auto;
                background-color: #fff;
                > li {
                    background-color: #fff;
                }
            }
            &.collapse {
                .file-title {
                    background-color: #E7ECF2;
                }
            }
        }
        .no-file {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            font-size: 16px;
        }
        .uploader-file-icon {
            &:before {
                content: '' !important;
            }
            &[icon=image] {
                background: url(../../../static/images/image-icon.png);
            }
            &[icon=video] {
                background: url(../../../static/images/video-icon.png);
            }
            &[icon=document] {
                background: url(../../../static/images/text-icon.png);
            }
        }
        .uploader-file-actions > span {
            margin-right: 6px;
        }
    }
    /* 隐藏上传按钮 */
    #global-uploader-btn {
        position: absolute;
        clip: rect(0, 0, 0, 0);
    }
</style>
